﻿//-------------------------------------------------------------------
//	Copyright (c) 2010-2014  Zhirnov Andrey
//	This file is part of the "UX Standard Lib" project.
//	See copyright notice in "UX_STL_Ext.h".
//-------------------------------------------------------------------

#ifndef _UXS_GL_MATRIX_STACK_H
#define _UXS_GL_MATRIX_STACK_H

#pragma once

#include "../shell/GLStateManager.h"

namespace UX_STL
{
namespace GLShell
{
#ifdef UX_MSTACK_CACHE_DISABLE
#	define MSTACK_CACHE( _check )
#else
#	define MSTACK_CACHE( _check )	_check
#endif
	

	// Matrix Transform Type
	namespace e_transform
	{
		enum	type :	uint8
		{
			VIEW		= 0,
			MODEL,
			PROJECTION,
			TEXTURE_0,
			TEXTURE_1,
			TEXTURE_2,
			TEXTURE_3,
			TEXTURE_4,
			TEXTURE_5,
			TEXTURE_6,
			TEXTURE_7,
			TEXTURE_8,
			TEXTURE_9,
			SHADOW_0,
			SHADOW_1,
			SHADOW_2,
			SHADOW_3,
			SHADOW_4,
			SHADOW_5,
			_COUNT,
		};

		UX_ENUM_ADD_OPERATIONS( type )
	}




	//
	// Matrix Stack
	//

	class GLMatrixStack
	{
	private:
		enum EMaterixType
		{
			MODEL_VIEW			= 0,
			VIEW_PROJECTION,
			MODEL_VIEW_PROJECTION,
			NORMAL,

			MODEL_VIEW_INVERSE,
			VIEW_PROJECTION_INVERSE,
			MODEL_VIEW_PROJECTION_INVERSE,
			NORMAL_INVERSE,

			MODEL_INVERSE,
			VIEW_INVERSE,
			PROJECTION_INVERSE,

			_COUNT
		};

		typedef array< fmat4 >				matrix_array_t;
		typedef static_stack< fmat4, 16 >	matrix_stack_t;
		typedef array< matrix_stack_t >		matrix_stack_arr_t;

	private:
		matrix_stack_arr_t		_aMatrices;
		// cache //
		fmat4					_sMVMatrix,
								_sVPMatrix,
								_sMVPMatrix;
		fmat4					_sMVMatrixInverse,
								_sVPMatrixInverse,
								_sMVPMatrixInverse;
		fmat4					_sModelInverse,
								_sViewInverse,
								_sProjInverse;
		fmat3					_sNormalMatrix,
								_sNormalMatrixInverse;
		
		TBitfield< _COUNT >		_sMatricesChanged;
		
	private:
		void _OnMatrixChanged(e_transform::type eType);

	public:
		GLMatrixStack();
		~GLMatrixStack();

		void Reset();

		void MultMatrix(const fmat4 &sMatrix, e_transform::type eState);
		void SetMatrix(const fmat4 &sMatrix, e_transform::type eState);

		const fmat4 & GetMatrix(e_transform::type eState) const;
		const fmat4 & GetMatrixMVP();
		const fmat4 & GetMatrixMV();
		const fmat4 & GetMatrixVP();
		const fmat3 & GetMatrixNormal();
		
		const fmat4 & GetMatrixModelInverse();
		const fmat4 & GetMatrixViewInverse();
		const fmat4 & GetMatrixProjInverse();
		const fmat4 & GetMatrixMVPInverse();
		const fmat4 & GetMatrixMVInverse();
		const fmat4 & GetMatrixVPInverse();
		const fmat3 & GetMatrixNormalInverse();

		void PushMatricesMVP();
		void PopMatricesMVP();
		void PushMatrix(e_transform::type eState);
		void PopMatrix(e_transform::type eState);

		// set cache matrices
		void SetMatrixMVP(const fmat4 &sMatrix);
		void SetMatrixMV(const fmat4 &sMatrix);
		void SetMatrixVP(const fmat4 &sMatrix);
		void SetMatrixNormal(const fmat3 &sMatrix);

		void SetMatrixMVP(const fmat4 &sModel, const fmat4 &sView, const fmat4 &sProj, const fmat4 &sMVP);
		void SetMatrixMV(const fmat4 &sModel, const fmat4 &sView, const fmat4 &sMV);
		void SetMatrixVP(const fmat4 &sView, const fmat4 &sProj, const fmat4 &sVP);
	};

	

/*
=================================================
	
=================================================
*/
	inline GLMatrixStack::GLMatrixStack(): _sMatricesChanged(0)
	{
		_aMatrices.Resize( e_transform::_COUNT, false );

		_aMatrices[e_transform::VIEW].SetDefault( fmat4::Identity() );
		_aMatrices[e_transform::MODEL].SetDefault( fmat4::Identity() );
		_aMatrices[e_transform::PROJECTION].SetDefault( fmat4::Identity() );

		for (usize i = e_transform::TEXTURE_0; i < _aMatrices.Count(); ++i)
		{
			_aMatrices[i].SetDefault( fmat4::Identity() );
		}
	}
	
/*
=================================================
	
=================================================
*/
	inline GLMatrixStack::~GLMatrixStack()
	{
		_aMatrices.Clear();
	}
	
/*
=================================================
	
=================================================
*/
	inline void GLMatrixStack::Reset()
	{
		_sMatricesChanged = 0;

		FOR( i, _aMatrices ) {
			_aMatrices[i].Clear();
		}
	}

/*
=================================================
	
=================================================
*/
	inline void GLMatrixStack::MultMatrix(const fmat4 &sMatrix, e_transform::type eState)
	{
		ASSERT( eState < e_transform::_COUNT );

		_aMatrices[eState].Get() *= sMatrix;
		_OnMatrixChanged( eState );
	}
	
/*
=================================================
	
=================================================
*/
	inline void GLMatrixStack::SetMatrix(const fmat4 &sMatrix, e_transform::type eState)
	{
		ASSERT( eState < e_transform::_COUNT );

		_aMatrices[eState].Set( sMatrix );
		_OnMatrixChanged( eState );
	}
/*
=================================================
	
=================================================
*/
	inline void GLMatrixStack::PushMatrix(e_transform::type eState)
	{
		_aMatrices[eState].Push();
	}
	
	inline void GLMatrixStack::PushMatricesMVP()
	{
		_aMatrices[e_transform::MODEL].Push();
		_aMatrices[e_transform::VIEW].Push();
		_aMatrices[e_transform::PROJECTION].Push();
	}

/*
=================================================
	
=================================================
*/
	inline void GLMatrixStack::PopMatrix(e_transform::type eState)
	{
		_aMatrices[eState].Pop();
		_OnMatrixChanged( eState );
	}
	
	inline void GLMatrixStack::PopMatricesMVP()
	{
		_aMatrices[e_transform::MODEL].Pop();
		_aMatrices[e_transform::VIEW].Pop();
		_aMatrices[e_transform::PROJECTION].Pop();

		_OnMatrixChanged( e_transform::MODEL );
		_OnMatrixChanged( e_transform::VIEW );
		_OnMatrixChanged( e_transform::PROJECTION );
	}
	
/*
=================================================
	
=================================================
*/
	inline void GLMatrixStack::_OnMatrixChanged(e_transform::type eType)
	{
		MSTACK_CACHE
		(
			if ( eType >= e_transform::TEXTURE_0 )
				return;

			bool const	b_model	= eType == e_transform::MODEL;
			bool const	b_view	= eType == e_transform::VIEW;
			bool const	b_proj	= eType == e_transform::PROJECTION;

			bool const	b_mv	= b_model | b_view;
			bool const	b_vp	= b_view  | b_proj;
			bool const	b_mvp	= b_mv | b_vp;
			uint		bits	= 0;

			bits |= b_mv  << MODEL_VIEW;
			bits |= b_vp  << VIEW_PROJECTION;
			bits |= b_mvp << MODEL_VIEW_PROJECTION;
			bits |= b_mv  << NORMAL;
		
			bits |= b_mv  << MODEL_VIEW_INVERSE;
			bits |= b_vp  << VIEW_PROJECTION_INVERSE;
			bits |= b_mvp << MODEL_VIEW_PROJECTION_INVERSE;
			bits |= b_mv  << NORMAL_INVERSE;

			bits |= b_model << MODEL_INVERSE;
			bits |= b_view << VIEW_INVERSE;
			bits |= b_proj << PROJECTION_INVERSE;

			_sMatricesChanged |= bits;
		);
	}

/*
=================================================
	
=================================================
*/
	inline const fmat4 & GLMatrixStack::GetMatrix(e_transform::type eState) const
	{
		return _aMatrices[eState].Get();
	}
	
/*
=================================================
	
=================================================
*/
	inline const fmat4 & GLMatrixStack::GetMatrixMVP()
	{
		MSTACK_CACHE(
		if ( _sMatricesChanged.Get( MODEL_VIEW_PROJECTION ) ) )
		{
			_sMatricesChanged.Reset( MODEL_VIEW_PROJECTION );
			_sMVPMatrix = GetMatrixVP() * _aMatrices[e_transform::MODEL].Get();
		}
		return _sMVPMatrix;
	}
	
/*
=================================================
	
=================================================
*/
	inline const fmat4 & GLMatrixStack::GetMatrixMV()
	{
		MSTACK_CACHE(
		if ( _sMatricesChanged.Get( MODEL_VIEW ) ) )
		{
			_sMatricesChanged.Reset( MODEL_VIEW );
			_sMVMatrix = _aMatrices[e_transform::VIEW].Get() *
						 _aMatrices[e_transform::MODEL].Get();
		}
		return _sMVMatrix;
	}
	
/*
=================================================
	
=================================================
*/
	inline const fmat4 & GLMatrixStack::GetMatrixVP()
	{
		MSTACK_CACHE(
		if ( _sMatricesChanged.Get( VIEW_PROJECTION ) ) )
		{
			_sMatricesChanged.Reset( VIEW_PROJECTION );
			_sVPMatrix = _aMatrices[e_transform::PROJECTION].Get() *
						 _aMatrices[e_transform::VIEW].Get();
		}
		return _sVPMatrix;
	}
	
/*
=================================================
	
=================================================
*/
	inline const fmat3 & GLMatrixStack::GetMatrixNormal()
	{
		MSTACK_CACHE(
		if ( _sMatricesChanged.Get( NORMAL ) ) )
		{
			_sMatricesChanged.Reset( NORMAL );
			_sNormalMatrix = GetMatrixMV().To<fmat3>().Inverse().Transpose();
		}
		return _sNormalMatrix;
	}
	
/*
=================================================
	
=================================================
*/
	inline const fmat4 & GLMatrixStack::GetMatrixModelInverse()
	{
		MSTACK_CACHE(
		if ( _sMatricesChanged.Get( MODEL_INVERSE ) ) )
		{
			_sMatricesChanged.Reset( MODEL_INVERSE );
			_sModelInverse = GetMatrix( e_transform::MODEL ).Inverse();
		}
		return _sModelInverse;
	}
	
/*
=================================================
	
=================================================
*/
	inline const fmat4 & GLMatrixStack::GetMatrixViewInverse()
	{
		MSTACK_CACHE(
		if ( _sMatricesChanged.Get( VIEW_INVERSE ) ) )
		{
			_sMatricesChanged.Reset( VIEW_INVERSE );
			_sViewInverse = GetMatrix( e_transform::VIEW ).Inverse();
		}
		return _sViewInverse;
	}
	
/*
=================================================
	
=================================================
*/
	inline const fmat4 & GLMatrixStack::GetMatrixProjInverse()
	{
		MSTACK_CACHE(
		if ( _sMatricesChanged.Get( PROJECTION_INVERSE ) ) )
		{
			_sMatricesChanged.Reset( PROJECTION_INVERSE );
			_sProjInverse = GetMatrix( e_transform::PROJECTION ).Inverse();
		}
		return _sProjInverse;
	}

/*
=================================================
	
=================================================
*/
	inline const fmat4 & GLMatrixStack::GetMatrixMVPInverse()
	{
		MSTACK_CACHE(
		if ( _sMatricesChanged.Get( MODEL_VIEW_PROJECTION_INVERSE ) ) )
		{
			_sMatricesChanged.Reset( MODEL_VIEW_PROJECTION_INVERSE );
			_sMVPMatrixInverse = GetMatrixMVP().Inverse();
		}
		return _sMVPMatrixInverse;
	}

/*
=================================================
	
=================================================
*/
	inline const fmat4 & GLMatrixStack::GetMatrixMVInverse()
	{
		MSTACK_CACHE(
		if ( _sMatricesChanged.Get( MODEL_VIEW_INVERSE ) ) )
		{
			_sMatricesChanged.Reset( MODEL_VIEW_INVERSE );
			_sMVMatrixInverse = GetMatrixMV().Inverse();
		}
		return _sMVMatrixInverse;
	}

/*
=================================================
	
=================================================
*/
	inline const fmat4 & GLMatrixStack::GetMatrixVPInverse()
	{
		MSTACK_CACHE(
		if ( _sMatricesChanged.Get( VIEW_PROJECTION_INVERSE ) ) )
		{
			_sMatricesChanged.Reset( VIEW_PROJECTION_INVERSE );
			_sVPMatrixInverse = GetMatrixVP().Inverse();
		}
		return _sVPMatrixInverse;
	}

/*
=================================================
	
=================================================
*/
	inline const fmat3 & GLMatrixStack::GetMatrixNormalInverse()
	{
		MSTACK_CACHE(
		if ( _sMatricesChanged.Get( NORMAL_INVERSE ) ) )
		{
			_sMatricesChanged.Reset( NORMAL_INVERSE );
			_sNormalMatrixInverse = GetMatrixNormal().Inverse();
		}
		return _sNormalMatrixInverse;
	}

/*
=================================================
	
=================================================
*/
	inline void GLMatrixStack::SetMatrixMVP(const fmat4 &sMatrix)
	{
		_sMatricesChanged.Reset( MODEL_VIEW_PROJECTION );
		_sMVPMatrix = sMatrix;
	}
	
/*
=================================================
	
=================================================
*/
	inline void GLMatrixStack::SetMatrixMV(const fmat4 &sMatrix)
	{
		_sMatricesChanged.Reset( MODEL_VIEW );
		_sMVMatrix = sMatrix;
	}
	
/*
=================================================
	
=================================================
*/
	inline void GLMatrixStack::SetMatrixVP(const fmat4 &sMatrix)
	{
		_sMatricesChanged.Reset( VIEW_PROJECTION );
		_sVPMatrix = sMatrix;
	}
	
/*
=================================================
	
=================================================
*/
	inline void GLMatrixStack::SetMatrixNormal(const fmat3 &sMatrix)
	{
		_sMatricesChanged.Reset( NORMAL );
		_sNormalMatrix = sMatrix;
	}
	
/*
=================================================
	
=================================================
*/
	inline void GLMatrixStack::SetMatrixMVP(const fmat4 &sModel, const fmat4 &sView, const fmat4 &sProj, const fmat4 &sMVP)
	{
		_aMatrices[ e_transform::MODEL ].Set( sModel );
		_aMatrices[ e_transform::VIEW  ].Set( sView  );
		_aMatrices[ e_transform::PROJECTION ].Set( sProj );
		
		_OnMatrixChanged( e_transform::MODEL );
		_OnMatrixChanged( e_transform::VIEW );
		_OnMatrixChanged( e_transform::PROJECTION );

		SetMatrixMVP( sMVP );
	}
	
/*
=================================================
	
=================================================
*/
	inline void GLMatrixStack::SetMatrixMV(const fmat4 &sModel, const fmat4 &sView, const fmat4 &sMV)
	{
		_aMatrices[ e_transform::MODEL ].Set( sModel );
		_aMatrices[ e_transform::VIEW  ].Set( sView  );
		
		_OnMatrixChanged( e_transform::MODEL );
		_OnMatrixChanged( e_transform::VIEW );

		SetMatrixMV( sMV );
	}
	
/*
=================================================
	
=================================================
*/
	inline void GLMatrixStack::SetMatrixVP(const fmat4 &sView, const fmat4 &sProj, const fmat4 &sVP)
	{
		_aMatrices[ e_transform::VIEW  ].Set( sView  );
		_aMatrices[ e_transform::PROJECTION ].Set( sProj );
		
		_OnMatrixChanged( e_transform::VIEW );
		_OnMatrixChanged( e_transform::PROJECTION );

		SetMatrixVP( sVP );
	}

	
}	// GLShell


	UX_ENUM_META_OBJECT( GLShell::e_transform::type );
	
//-------------------------------------------------------------------

}	// UX_STL

#endif	// _UXS_GL_MATRIX_STACK_H